/*
 * Propagate.java
 *
 * Created on Jan 9, 2012
 *
 * Copyright (c) 2011 Artur Rataj.
 *
 * This code is distributed under the terms of the GNU Library
 * General Public License, either version 3 of the license or, at
 * your option, any later version.
 */

package pl.gliwice.iitis.spatialnn.propagate;

import pl.gliwice.iitis.spatialnn.network.Network;

import java.util.*;

import pl.gliwice.iitis.spatialnn.network.*;

/**
 * A class for propagation of signals through a network.
 * 
 * @author Artur Rataj
 */
public class Propagate {
    /**
     * Network to propagate through.
     */
    public Network network;
    
    /**
     * Creates a new instance of <code>Propagate</code>.<br>
     * 
     * Synces all neurons and membranes.
     */
    public Propagate(Network network) {
        this.network = network;
        syncComponents();
    }
    /**
     * Synchronises <code>network</code>.
     */
    private void syncComponents() {
        network.sync();
    }
    /**
     * Returns a new cache for <code>get()<code>. A single cache can not
     * be used by two threads, that access <code>get()</code> simultaneously.
     * 
     * @return a new cache object
     */
    public Cache getCache() {
        return new Cache(network);
    }
    /**
     * Propagates given signals through the network.
     * 
     * @param in input data, keyed with membrane keys
     * @param cache separate cache for each thread, that uses this method
     * simultaneously with other threads
     * @return output data, in the first n elements, where n is the resolution
     * of the output membrane
     */
    public double[] get(Map<String, double[]> in, Cache cache) {
        Map<Membrane, double[]> outB = cache.outB;
        if(in.size() != network.membranes.size() - 1)
            throw new RuntimeException("mismatch of the number of inputs");
        // propagate through input membranes
        for(String key : in.keySet()) {
            Membrane m = network.membraneMap.get(key);
            if(m.type != MembraneType.IN)
                throw new RuntimeException("membrane must be of type input");
            double[] o = outB.get(m);
            m.propagate(in.get(m.key), o);
            Signal[] s = cache.outS.get(m);
            int[] i = cache.outI.get(m);
            for(int count = 0; count < s.length; ++count)
{
if(s[count].in.length <= i[count] || o.length <= count)
    i = i;
                s[count].in[i[count]] = o[count];
}
        }
        // propagate through layers
        double[] inB = cache.inB;
        int maxLayer = network.getMaxLayer();
        for(int layerNum = maxLayer; layerNum >= 0; --layerNum) {
            Map<Neuron, Signal> map = cache.layers.get(layerNum);
            if(map != null) {
                for(Signal s : map.values()) {
                    double v = s.neuron.propagate(s.in);
                    for(SignalOutput so : s.outputs) {
                        if(so.outMembrane != null)
                            inB[so.outIndex] = v;
                        else
                            so.outSignal.in[so.outIndex] = v;
                    }
                }
            }
        }
        // propagate through the output membrane
        double[] out = outB.get(cache.outM);
        cache.outM.propagate(inB, out);
        return out;
    }
}
